package com.lipaluma.field.validator.impl.numeric;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

import com.lipaluma.annotations.validations.ValidateDecimal;
import com.lipaluma.context.Context;
import com.lipaluma.exceptions.CsvParserException;
import com.lipaluma.field.validator.ValidationError;
import com.lipaluma.field.validator.impl.generic.AbstractIsIncludedInValidator;
import com.lipaluma.xml.validator.XmlIncludedInValidator;
import com.lipaluma.xml.validator.XmlValidator;

/**
 * Validate a decimal field to check that the value of the field must be included in at least one of differents given values.<br/>
 * 
 * <p>
 * For Initialisation :
 * <ul>
 * 	<li>
 * 	<u><b>By annotations :</b></u> Use {@link ValidateDecimal}, with the attribute <code>includedIn = {15.25, 17.52, 20.547}</code> to specify the list of
 * values accepted. <br/>
 * The attribute precision may be used too.
 * </li>
 * 	<li>
 * 	<u><b>By Xml :</b></u>
 * <ul>
 * <li>
 * This validator may be added using xml mapping with the element <code>excludedOf</code> like below : <br/>
 * <code>
 * &lt;ToMap field="..."   ...><br/>
 * <b>
 * &nbsp;&nbsp;&nbsp;&lt;includedIn precision="1"><br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;value>15.25&lt;/value><br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;value>17.52&lt;/value><br/>
 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;value>20.547&lt;/value><br/>
 * &nbsp;&nbsp;&nbsp;&lt;/includedIn>
 * </b><br/>
 * &lt;/ToMap>
 * </code>
 * <br/>
 * In this example, we have added the optional parameter precision to give 
 * to compare the decimals at the given precision.
 * </li>
 * </li>
 * </ul>
 * @author Mario
 *
 */
public class DIncludedInValidator extends AbstractIsIncludedInValidator<Double> {
	private Integer precision = null;

	public DIncludedInValidator() {}
	public DIncludedInValidator(double... values) {
		if(values == null)
			throw new CsvParserException("Error on setting "+this.getClass()+" : given values can't be null");			
		List<Double> possibleNumbers = new ArrayList<Double>();
		for (Double number : values) {
			possibleNumbers.add(number);
		}
		init(possibleNumbers);
	}
	
	@Override
	public boolean init(Context context, Field field) {
		ValidateDecimal annotation = field.getAnnotation(ValidateDecimal.class);
		if(annotation == null || annotation.includedIn().length == 0)
			return false;
		if(annotation.precision() >= 0) 
			this.precision = annotation.precision();
		List<Double> possibleNumbers = new ArrayList<Double>();
		for (Double number : annotation.includedIn()) {
			possibleNumbers.add(roundValueByPrecision(number, precision));
		}
		init(possibleNumbers);
		return true;
	}
	
	@Override
	public boolean init(Context context, Field field, XmlValidator xmlValidator) {		
		XmlIncludedInValidator validator = (XmlIncludedInValidator) xmlValidator;
		this.precision = validator.getPrecision();		
		List<Double> includedNumbers = new ArrayList<Double>();
		for(String string : validator.getValues()) {
			Double value = Double.parseDouble(string);
			includedNumbers.add(roundValueByPrecision(value, this.precision)); 
		}
		init(includedNumbers);
		return true;
	}

	@Override
	public boolean validate(Object value, List<ValidationError> errors) {
		return super.validate(roundValueByPrecision(new Double(value.toString()), precision), errors);
	}	

	private Double roundValueByPrecision(Double value, Integer precision) {
		if(precision != null)
			return new BigDecimal(String.valueOf(value)).setScale(precision, RoundingMode.HALF_DOWN).doubleValue();				
		else 
			return value;
	}
}
